// 定义一系列函数相关内容
package cf

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"gitee.com/ctyunfaas/cf-runtime-go-sdk/config"
	"gitee.com/ctyunfaas/cf-runtime-go-sdk/fccontext"
	"gitee.com/ctyunfaas/cf-runtime-go-sdk/util"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
)

func newFunction(handler interface{}, funcType string) *userFunction {
	f := &userFunction{
		funcType: funcType,
	}
	if f.funcType == config.EventFunction {
		f.handler = NewHandler(handler)
	} else {
		f.httpHandler = NewHttpHandler(handler)
	}
	return f
}

// 服务处理请求入口 handler，存储用户函数相关信息
type userFunction struct {
	// 用户函数类型（event/http）和对应的函数
	funcType    string
	handler     Handler
	httpHandler HttpHandler
	li          *util.LoggerInterceptor

	// 用户注册的回调函数
	initializeHandler LifeCycleHandler
	preFreezeHandler  LifeCycleHandler
	preStopHandler    LifeCycleHandler
}

// 实现 http.handler 接口，runtime server 进行请求处理的入口
func (uf *userFunction) requestHandler(w http.ResponseWriter, req *http.Request) {
	if sem != nil {
		sem <- struct{}{} // 并发数达到最大时，将阻塞于此
	}

	// 配置 log 输出到拦截器
	loggerInterceptor := util.NewLoggerInterceptor()
	uf.li = loggerInterceptor
	useTail := false
	if req.Header.Get(config.LOG_TYPE) == "Tail" {
		log.SetOutput(loggerInterceptor)
		useTail = true
	}
	customResponseWriter := util.NewCustomResponseWriter(w, useTail, loggerInterceptor)

	defer func() {
		customResponseWriter.FinishResponse() // 返回消息给客户端（保证 tail log 收集到所有日志）
		if sem != nil {
			<-sem // 释放一个信号量
		}
	}()
	if err := handleRequest(customResponseWriter, req, uf); err != nil {
		// 统一错误日志
		logPrintf("failed to invoke function due to %v", err)
	}
}

// 用于处理单个请求
func handleRequest(w http.ResponseWriter, req *http.Request, uf *userFunction) error {

	// 解析环境变量中函数相关信息和 request 内容，将请求内容包装一个传入用户函数的 context
	functionRequest, err := genFunctionRequest(req, w)
	if err != nil {
		return fmt.Errorf("unexpected error occurred when parsing request: %v", err)
	}

	ltype := handleFunction
	if req.Method == "POST" && req.URL.Path == "/initialize" {
		ltype = initializerFunction
	} else if req.Method == "GET" && req.URL.Path == "/pre-stop" {
		ltype = preStopFunction
	}

	// 创建一个 response 对象，传递给用户函数填充，以便统一响应格式和错误处理
	funcResponse := &InvokeResponse{}

	// 调用 userFunction 的 Invoke 执行用户函数
	ivkErr := uf.Invoke(funcResponse, functionRequest, ltype, w) // 如果 ivkErr 不为空说明发生了 panic
	if err := funcResponse.Error; err != nil {
		// 用户函数执行出错，返回错误响应
		w.Header().Set("Content-Type", config.ContentTypeJSON)
		w.Header().Set(config.ERR_Header, config.UnhandledInvocationError)
		w.WriteHeader(config.HTTP_SERVER_ERROR)
		w.Write([]byte(err.ToJson()))
		if err.ShouldExit { // 遇见 panic 该位置会被标记
			return fmt.Errorf("calling the handler function resulted in a panic")
		}
		return ivkErr
	}
	return ivkErr
}

// 调用用户函数，回复响应
func (uf *userFunction) Invoke(resp *InvokeResponse, freq *functionRequest, lifeType int, w http.ResponseWriter) (err error) {
	defer func() {
		if e := recover(); e != nil {
			resp.Error = fcPanicResponse(e)
			uf.printPanicLog(freq.RequestId, resp.Error.ToJson())
			uf.printEndLog(lifeType, freq.RequestId, false)
			err = fmt.Errorf("%v", e)
		} else if resp.Error != nil {
			uf.printPanicLog(freq.RequestId, resp.Error.ToJson())
			uf.printEndLog(lifeType, freq.RequestId, false)
		} else {
			uf.printEndLog(lifeType, freq.RequestId, true)
		}
	}()

	ctx := context.WithValue(context.Background(), config.LOG_TYPE, uf.li) // context 中加上拦截器
	invokeContext := fccontext.NewContext(ctx, &freq.Context)

	uf.printStartLog(lifeType, freq.RequestId) // 打印日志，表示用户函数开始执行
	if lifeType == initializerFunction {
		if uf.initializeHandler == nil {
			uf.initializeHandler = errorLifeCycleHandler(errors.New("no initializer handler registered"))
		}
		uf.initializeHandler.Invoke(invokeContext)
		return nil
	} else if lifeType == preStopFunction {
		if uf.preStopHandler == nil {
			uf.preStopHandler = errorLifeCycleHandler(errors.New("no preStop handler registered"))
		}
		uf.preStopHandler.Invoke(invokeContext)
		return nil
	} else if uf.funcType == config.HttpFunction {
		return uf.invokeHttpFunc(invokeContext, freq, resp, w)
	}
	return uf.invokeEventFunc(invokeContext, freq, resp, w)
}

func (uf *userFunction) invokeHttpFunc(ctx context.Context, freq *functionRequest, errResp *InvokeResponse, w http.ResponseWriter) error {
	// 统一响应格式，创建一个 http response 对象，用户返回相应内容到 response 中
	resp := newFcResponse()

	if err := uf.httpHandler.Invoke(ctx, resp, freq.Request); err != nil {
		errResp.Error = fcErrorResponse(err)
		return nil
	}

	// 执行成功，返回相应内容
	WriteResponse(w, resp)

	return nil
}

func (uf *userFunction) invokeEventFunc(ctx context.Context, freq *functionRequest, errResp *InvokeResponse, w http.ResponseWriter) error {
	respPayload, err := uf.handler.Invoke(ctx, freq.Payload)
	if err != nil {
		errResp.Error = fcErrorResponse(err)
		return nil
	}
	// 执行成功，返回响应
	w.Header().Set("Content-Type", config.ContentTypeJSON)
	w.Write(respPayload)
	return nil
}

func (uf *userFunction) registryLifeCycleHandler(lifeCycleHandlers []lifeCycleWrapper) {
	for _, item := range lifeCycleHandlers {
		switch item.lifeType {
		case initializerFunction:
			uf.initializeHandler = newLifeCycleHandler(item.lcHandler)
		case preStopFunction:
			uf.preStopHandler = newLifeCycleHandler(item.lcHandler)
		default:
			logPrintf("unknow lifeType: %v", item.lifeType)
		}
	}
}

type functionRequest struct {
	Payload   []byte
	RequestId string
	Context   fccontext.FcContext
	Request   *http.Request
}

func copyRequest(original *http.Request) (*http.Request, error) {
	// 创建一个新的 Request 实例
	r := new(http.Request)
	*r = *original

	// 复制 Header
	r.Header = make(http.Header, len(original.Header))
	for k, v := range original.Header {
		r.Header[k] = append([]string(nil), v...)
	}

	// 复制 URL
	if original.URL != nil {
		r.URL = new(url.URL)
		*r.URL = *original.URL
	}

	// 复制 Body
	if original.Body != nil {
		bodyBytes, err := ioutil.ReadAll(original.Body)
		if err != nil {
			return nil, err
		}
		// 重要：确保重新设置原始请求的 Body
		original.Body = ioutil.NopCloser(bytes.NewReader(bodyBytes))
		r.Body = ioutil.NopCloser(bytes.NewReader(bodyBytes))
	}

	return r, nil
}

// 提取 Context, Payload, RequestId 便于后续调用
func genFunctionRequest(req *http.Request, w http.ResponseWriter) (*functionRequest, error) {
	reqID := req.Header.Get(config.REQUEST_ID)
	if reqID == "" {
		reqID = config.DefaultRequestID
	}
	w.Header().Set(config.H_RETURN_REQUEST_ID, reqID) // 解析 requestID 的同时配置 response header

	cpReq, _ := copyRequest(req)

	bodyBytes, err := ioutil.ReadAll(req.Body)
	defer req.Body.Close()
	if err != nil {
		log.Fatalf("Error reading body: %v", err)
	}

	res := &functionRequest{
		RequestId: reqID,
		Payload:   bodyBytes,
		Context: fccontext.FcContext{
			RequestID: reqID,
			Credentials: fccontext.Credentials{
				AccessKeyId:     req.Header.Get(config.H_ACCESS_KEY_ID),
				AccessKeySecret: req.Header.Get(config.H_ACCESS_KEY_SECRET),
			},
			Function: fccontext.Function{
				Name:    req.Header.Get(config.H_FUNC_NAME),
				Handler: req.Header.Get(config.H_FUNC_HANDLER),
				Memory:  req.Header.Get(config.H_FUNC_MEMORY),
			},
			Region:    req.Header.Get(config.H_REGION),
			AccountId: req.Header.Get(config.H_ACCOUNT_ID),
		},
		Request: cpReq,
	}

	return res, nil
}

func (uf *userFunction) printPanicLog(requestId, errorMessage string) {
	if !enableInvokePanicLog {
		return
	}
	logPrintf(" %s [ERROR] %s\n", requestId, errorMessage)
}

func (uf *userFunction) printEndLog(funcType int, requestId string, isHandled bool) {
	suffix := ""
	if !isHandled {
		suffix = ", Error: Unhandled function error"
	}

	switch funcType {
	case initializerFunction:
		logPrintf("FC Initialize End RequestId: %s%s\n", requestId, suffix)
	case preStopFunction:
		logPrintf("FC PreStop End RequestId: %s%s\n", requestId, suffix)
	default:
		logPrintf("FC Invoke End RequestId: %s%s\n", requestId, suffix)
	}
}

func (uf *userFunction) printStartLog(funcType int, requestId string) {
	switch funcType {
	case initializerFunction:
		logPrintf("FC Initialize Start RequestId: %s\n", requestId)
	case preStopFunction:
		logPrintf("FC PreStop Start RequestId: %s\n", requestId)
	default:
		logPrintf("FC Invoke Start RequestId: %s\n", requestId)
	}
}
